package org.zjt.demo.config;


import org.apache.http.util.Asserts;
import org.quartz.*;
import org.quartz.spi.TriggerFiredBundle;
import org.springframework.beans.BeansException;
import org.springframework.beans.factory.config.AutowireCapableBeanFactory;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationContextAware;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.io.ClassPathResource;
import org.springframework.jdbc.datasource.DataSourceTransactionManager;
import org.springframework.scheduling.quartz.SchedulerFactoryBean;
import org.springframework.scheduling.quartz.SpringBeanJobFactory;
import org.springframework.util.Assert;
import org.springframework.util.CollectionUtils;
import org.zjt.demo.config.properties.JobProperties;

import javax.sql.DataSource;
import java.util.HashSet;
import java.util.List;
import java.util.Objects;
import java.util.Set;
import java.util.concurrent.*;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.stream.Collectors;

/**
 * DESC
 *
 * @author
 * @create 2017-04-17 下午2:25
 **/
@Configuration
@EnableConfigurationProperties(QuartzProperties.class)
public class SchedulerConfigurer {


    @Bean
    public SchedulerFactoryBean schedulerFactoryBean(DataSource dataSource, DataSourceTransactionManager dataSourceTransactionManager,
                                                     QuartzProperties quartzProperties, AutowiringSpringBeanJobFactory autowiringSpringBeanJobFactory,
                                                     QuartzJobLoader quartzJobLoader) {
        Assert.notNull(dataSource, "datasource ");
        Assert.notNull(dataSourceTransactionManager, "dataSourceTransactionManager ");
        SchedulerFactoryBean schedulerFactoryBean = new SchedulerFactoryBean();
        schedulerFactoryBean.setDataSource(dataSource);
        ExecutorService executorService = new ThreadPoolExecutor(1, 5, 60, TimeUnit.SECONDS, new LinkedBlockingDeque<>(), new ScheduleThreadFactory());
        schedulerFactoryBean.setTaskExecutor(executorService);
        schedulerFactoryBean.setTransactionManager(dataSourceTransactionManager);
        schedulerFactoryBean.setConfigLocation(new ClassPathResource("quartz.properties"));
        schedulerFactoryBean.setAutoStartup(true);
        schedulerFactoryBean.setStartupDelay(30);
        schedulerFactoryBean.setOverwriteExistingJobs(true);
        schedulerFactoryBean.setSchedulerName("quartzScheduler");
        schedulerFactoryBean.setJobFactory(autowiringSpringBeanJobFactory);
        Set<CronTrigger> cronTriggers = new HashSet<>();
        Set<JobDetail> jobDetails = new HashSet<>();

        if (Objects.nonNull(quartzProperties) && !CollectionUtils.isEmpty(quartzProperties.getJobs())) {
            List<JobDetail> jobDetails1 = quartzProperties.getJobs().stream().map(this::jobProperties2JobDetail).collect(Collectors.toList());
            List<CronTrigger> cronTriggers1 = quartzProperties.getJobs().stream().map(this::jobProperties2ConTrigger).collect(Collectors.toList());
            cronTriggers.addAll(cronTriggers1);
            jobDetails.addAll(jobDetails1);
        }

        if (Objects.nonNull(quartzJobLoader.getQuartzProperties()) && !CollectionUtils.isEmpty(quartzJobLoader.getQuartzProperties().getJobs())) {
            List<JobProperties> jobs = quartzJobLoader.getQuartzProperties().getJobs();
            List<JobDetail> jobDetails2 = jobs.stream().map(this::jobProperties2JobDetail).collect(Collectors.toList());
            List<CronTrigger> cronTriggers2 = jobs.stream().map(this::jobProperties2ConTrigger).collect(Collectors.toList());
            cronTriggers.addAll(cronTriggers2);
            jobDetails.addAll(jobDetails2);
        }
        if (!CollectionUtils.isEmpty(cronTriggers) && !CollectionUtils.isEmpty(jobDetails)) {
            schedulerFactoryBean.setTriggers(cronTriggers.toArray(new CronTrigger[cronTriggers.size()]));
            schedulerFactoryBean.setJobDetails(jobDetails.toArray(new JobDetail[jobDetails.size()]));
        }
        Runtime.getRuntime().addShutdownHook(new Thread(() -> executorService.shutdown()));
        return schedulerFactoryBean;
    }


    @Bean
    public QuartzJobLoader quartzJobLoader() {
        return new QuartzJobLoader();
    }

    public CronTrigger jobProperties2ConTrigger(JobProperties jobProperties) {
        Asserts.notEmpty(jobProperties.getCron(), jobProperties + "JobProperties Cron ");
        CronScheduleBuilder scheduleBuilder = CronScheduleBuilder.cronSchedule(jobProperties.getCron());
        CronTrigger trigger = TriggerBuilder.newTrigger().withIdentity(jobProperties.getId()).forJob(jobProperties.getId())
                .withSchedule(scheduleBuilder.withMisfireHandlingInstructionFireAndProceed()).build();
        return trigger;
    }

    public JobDetail jobProperties2JobDetail(JobProperties jobProperties) {
        try {
            Asserts.notNull(jobProperties, "jobProperties");
            Class<?> aClass = Class.forName(jobProperties.getClazz());
            Asserts.check(Job.class.isAssignableFrom(aClass), aClass + "未实现 org.quartz.Job 接口");
            Asserts.notEmpty(jobProperties.getId(), jobProperties + "JobProperties id ");
            JobDetail jobDetail = JobBuilder.newJob((Class<? extends Job>) aClass).storeDurably(true).requestRecovery(true).withIdentity(jobProperties.getId()).build();
            if (!CollectionUtils.isEmpty(jobProperties.getJobDataMap()))
                jobDetail.getJobDataMap().putAll(jobProperties.getJobDataMap());
            return jobDetail;
        } catch (Exception e) {
            throw new RuntimeException(e);
        }
    }

    @Bean
    public AutowiringSpringBeanJobFactory autowiringSpringBeanJobFactory() {
        return new AutowiringSpringBeanJobFactory();
    }

}


class AutowiringSpringBeanJobFactory extends SpringBeanJobFactory implements ApplicationContextAware {
    private transient AutowireCapableBeanFactory beanFactory;

    @Override
    public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
        beanFactory = applicationContext.getAutowireCapableBeanFactory();
    }

    @Override
    protected Object createJobInstance(final TriggerFiredBundle bundle) throws Exception {
        final Object job = super.createJobInstance(bundle);
        beanFactory.autowireBean(job);
        return job;
    }
}


class ScheduleThreadFactory implements ThreadFactory {

    private static final AtomicInteger poolNumber = new AtomicInteger(1);
    private final ThreadGroup group;
    private final AtomicInteger threadNumber = new AtomicInteger(1);
    private final String namePrefix;

    ScheduleThreadFactory() {
        SecurityManager s = System.getSecurityManager();
        group = (s != null) ? s.getThreadGroup() : Thread.currentThread().getThreadGroup();
        namePrefix = "schedule-thread-" + poolNumber.getAndIncrement();
    }

    @Override
    public Thread newThread(Runnable r) {
        Thread thread = new Thread(group, r, namePrefix + threadNumber.getAndIncrement(), 0);
        if (thread.isDaemon())
            thread.setDaemon(false);
        if (thread.getPriority() != Thread.NORM_PRIORITY)
            thread.setPriority(Thread.NORM_PRIORITY);
        return thread;
    }
}